import { TDataSource, TDataView, TDataViewBase, TPivotDataView } from "@/models/dataSources";
import { create as createDataSource } from "./dataSource";
import { create as createDataView, createPivotDataView } from "./dataView";
import { IDataset, IDataView, IPivotDataView } from "./types";
import { TSqlAnalyzeService } from "@/services/sqlAnalyzeServices";
import { App } from "@/models/app";
import { Ref } from "vue";
import { TCpId } from "@/models/types";
import { TComponentServices, iterComponent } from "@/services/componentServices";
import { TDbServices } from "@/services/dbServices";
import { ComponentTag } from "@/models/component";
import { createUpdateId, EChart } from "@/models/reactiveComponent";
import { ForeachContainer } from "@/models/containers";


export type TDatasetServices = ReturnType<typeof getServices>

export function getServices(app: App, services: {
    sqlAnalyze: TSqlAnalyzeService,
    component: TComponentServices,
    db: TDbServices,
}) {

    const datasetRel = createDataSetRelationship(app.dataSources, app.dataViews, services)

    registerFilters(app, datasetRel, services)

    function createSql(datasetName: string, requestorId: string): Ref<string> {
        const ds = datasetRel.getDataset(datasetName)
        return ds.toSqlWithFilters(requestorId)
    }

    function addFilter(cpid: TCpId, datasetName: string, expression: string) {
        datasetRel.getDataset(datasetName).addFilter(cpid, expression)
    }

    function removeFilters(cpid: TCpId, datasetName: string) {
        datasetRel.getDataset(datasetName).removeFilters(cpid)
    }



    return {
        createSql,
        addFilter,
        removeFilters,
        getDataset: datasetRel.getDataset,
        getAllDataset: datasetRel.getAllDataset,
    }

}


function createDataSetRelationship(dataSources: TDataSource[], dataViews: TDataViewBase[], services: {
    sqlAnalyze: TSqlAnalyzeService,
    component: TComponentServices,
    db: TDbServices,
}) {

    const dataSetMap = new Map<string, IDataset>()

    function getFromMapMaybeError(name: string) {
        const ds = dataSetMap.get(name)
        if (!ds) {
            throw new Error(`not found dataset[name:${name}] in mapping`);
        }
        return ds
    }

    dataSources.forEach(ds => {
        const dsObj = createDataSource(ds.name, { component: services.component })
        dataSetMap.set(ds.name, dsObj)
    })

    dataViews.forEach(dv => {

        switch (dv.type) {
            case 'sql':
                {
                    const sqlDv = dv as TDataView
                    const dvObj = createDataView(sqlDv.name, sqlDv.sql, sqlDv.excludeLinkages, { component: services.component, sqlAnalyze: services.sqlAnalyze })
                    dataSetMap.set(dv.name, dvObj)
                }
                break;

            case 'pivot':
                {
                    const sqlDv = dv as TPivotDataView
                    const dvObj = createPivotDataView(sqlDv.name, sqlDv.source, sqlDv.pivotOptions, sqlDv.excludeLinkages, services)
                    dataSetMap.set(dv.name, dvObj)
                }
                break;
            default:
                break;
        }

    })


    for (const ds of dataSetMap.values()) {
        if (ds.typeName === 'dataView') {
            const dv = ds as IDataView
            const tabs = services.sqlAnalyze.getTableNames(dv.sql)

            tabs.forEach(tab => {

                const ds = getFromMapMaybeError(tab)
                dv.addLinkageDataset(ds)
            })

        }

        if (ds.typeName === 'pivot-dataView') {
            const dv = ds as IPivotDataView
            const sourceDs = getFromMapMaybeError(dv.sourceDatasetName)
            dv.addLinkageDataset(sourceDs)
        }

    }

    function getDataset(name: string) {
        const ds = getFromMapMaybeError(name)
        return ds
    }

    function getAllDataset() {
        return Array.from(dataSetMap.values())
    }


    return {
        getDataset,
        getAllDataset,
    }
}


type TUpdateInfo = {
    table: string
    field: string
}

type TDatasetRel = ReturnType<typeof createDataSetRelationship>

function registerFilters(app: App, datasetRel: TDatasetRel, services: {
    sqlAnalyze: TSqlAnalyzeService,
    component: TComponentServices,
    db: TDbServices,
}) {

    for (const cp of iterComponent(app)) {
        if (cp.tag === ComponentTag.EChart) {
            const chartCp = cp as EChart

            chartCp.chartInfos.forEach((info, index) => {
                info.updateInfos.forEach(upInfo => {
                    const ds = datasetRel.getDataset(upInfo.table)
                    const chartOptId = createUpdateId(chartCp, index)
                    ds.initFilter(chartOptId)
                })
            })
            continue
        }

        if (cp.tag === ComponentTag.Foreach) {
            const foreachCp = cp as ForeachContainer
            services.sqlAnalyze.getTableNames(foreachCp.sql.sql).forEach(table => {
                const ds = datasetRel.getDataset(table)
                ds.initFilter(cp.id)
            })
            continue
        }


        if ('updateInfos' in cp) {
            (cp.updateInfos as TUpdateInfo[]).forEach(info => {
                const ds = datasetRel.getDataset(info.table)
                ds.initFilter(cp.id)
            })
        }

    }

}